// playwright_worker.js —— JSONL 协议 Worker（一次性覆盖即可）
// 关键更新：
// 1) 新增 send_keys 指令：向“当前激活窗口”发送按键（Windows PowerShell SendKeys）
// 2) 新增 stub_save_dialog / restore_dialog：在 Electron 主进程临时接管 dialog.showSaveDialog/Sync
// 3) 保留：launch_app / wait_for / click / type / press / chain_relaunch / exit
// 4) chain_relaunch 过滤 DevTools 标志，避免重复 --remote-debugging-port/--inspect

'use strict';

const { _electron: electron } = require('playwright');
const cp = require('child_process');
const readline = require('readline');

let currentApp = null;   // 当前受控 Electron app（launcher 或 studio）
let currentPage = null;  // 当前窗口页


function normalizeKeyCombo(seqRaw) {
  if (!seqRaw) return '';
  // 兼容 {ALT+F4} 这种历史写法：去掉外围大括号
  let s = String(seqRaw).trim().replace(/^\{(.+)\}$/, '$1');

  const mapSingle = {
    'ALT': 'Alt', 'CTRL': 'Control', 'CONTROL': 'Control', 'SHIFT': 'Shift',
    'CMD': 'Meta', 'WIN': 'Meta', 'META': 'Meta',
    'ENTER': 'Enter', 'ESC': 'Escape', 'ESCAPE': 'Escape',
    'TAB': 'Tab', 'SPACE': 'Space', 'BACKSPACE': 'Backspace',
    'DELETE': 'Delete', 'DEL': 'Delete', 'INSERT': 'Insert', 'INS': 'Insert',
    'HOME': 'Home', 'END': 'End',
    'PAGEUP': 'PageUp', 'PGUP': 'PageUp',
    'PAGEDOWN': 'PageDown', 'PGDN': 'PageDown',
    'UP': 'ArrowUp', 'DOWN': 'ArrowDown', 'LEFT': 'ArrowLeft', 'RIGHT': 'ArrowRight'
  };

  // ALT+F4 / CTRL+S / SHIFT+TAB 等支持
  return s.split('+').map(part => {
    const p = part.trim();
    // F1..F24 直接返回（Playwright 接受）
    if (/^F\d{1,2}$/i.test(p)) return p.toUpperCase();
    const up = p.toUpperCase();
    if (mapSingle[up]) return mapSingle[up];
    // 其它字符（比如 'A'、';'）保持原样，让 Playwright 处理
    // 但首字母大写更稳（如 'enter' -> 'Enter'）
    return p.length ? p[0].toUpperCase() + p.slice(1) : p;
  }).join('+');
}

// ---------- 工具函数 ----------
const sleep = (ms) => new Promise(r => setTimeout(r, ms));

async function firstWindow(app) {
  const wins = app.windows();
  return wins[0] || await app.firstWindow();
}

async function bringToFrontAndWait(page, selector, timeout) {
  await page.bringToFront();
  await page.locator(selector).waitFor({ state: 'visible', timeout });
}

async function safeClick(page, selector, timeout) {
  await bringToFrontAndWait(page, selector, timeout);
  await page.locator(selector).click({ timeout });
}

// 按引号分组拆 cmdline -> argv
function splitCmdLine(cmdline) {
  const re = /"([^"]+)"|(\S+)/g;
  const argv = [];
  let m;
  while ((m = re.exec(cmdline)) !== null) argv.push(m[1] || m[2]);
  return argv;
}

// 过滤 DevTools 标志，避免与 Playwright 注入的 flags 冲突
function stripDevtoolsFlags(args) {
  return (args || []).filter(a =>
    !/^--remote-debugging-port(=|$)/i.test(a) &&
    !/^--inspect(-brk)?(=|$)/i.test(a)
  );
}

// 找由 launcher 拉起的 Studio（取“看起来最新”的一条）-> {pid, cmdline} | null
function findSpawnedStudio() {
  try {
    const out = cp.execSync(
      `wmic process where (name='MaxOpticsStudio.exe') get ProcessId,CommandLine /FORMAT:LIST`,
      { encoding: 'utf8' }
    );
    const blocks = out.split(/\r?\n\r?\n/).map(x => x.trim()).filter(Boolean);
    const last = blocks[blocks.length - 1] || '';
    const pid = (last.match(/ProcessId=(\d+)/) || [])[1];
    const cmd = (last.match(/CommandLine=(.+)/) || [])[1];
    if (pid && cmd) return { pid: parseInt(pid, 10), cmdline: cmd };
  } catch {}
  return null;
}

// ---------- Windows SendKeys（用于系统弹窗） ----------
function execPwsh(script) {
  return new Promise((resolve, reject) => {
    // PowerShell EncodedCommand 需要 UTF-16LE
    const b64 = Buffer.from(script, 'utf16le').toString('base64');
    const ps = cp.spawn('powershell.exe', [
      '-NoProfile',
      '-NonInteractive',
      '-EncodedCommand',
      b64
    ], { windowsHide: true });

    let stderr = '';
    ps.stderr.on('data', d => { stderr += d.toString(); });
    ps.on('close', code => {
      if (code === 0) resolve();
      else reject(new Error(`PowerShell exited with code ${code}: ${stderr}`));
    });
  });
}

async function sendKeysWindows(rawText) {
  if (process.platform !== 'win32') {
    throw new Error('send_keys currently supports only Windows.');
  }

  // 尝试把 Electron 窗口抬前（系统“另存为”通常为模态，兜底处理）
  try { if (currentPage) await currentPage.bringToFront(); } catch {}

  // 占位写法：<ENTER>/<TAB>/<ESC> → {ENTER}/{TAB}/{ESC}
  let text = String(rawText)
    .replace(/<ENTER>/gi, '{ENTER}')
    .replace(/<TAB>/gi, '{TAB}')
    .replace(/<ESC>/gi, '{ESC}');

  // SendKeys 特殊符号（+^%~(){}）若要字面量，按 SendKeys 规则用 {} 包裹
  const ps = `
Add-Type -AssemblyName System.Windows.Forms | Out-Null;
[System.Windows.Forms.SendKeys]::SendWait("${text.replace(/"/g, '""')}");
`;

  await execPwsh(ps);
}

// 解析选择：支持 'OK'/'Yes'/'No' 等；若没匹配，默认选默认按钮
function pickButtonIndex(choice, options) {
  if (!options || !Array.isArray(options.buttons)) return options?.defaultId ?? 0;
  const target = String(choice || '').toLowerCase();
  let idx = options.buttons.findIndex(b => String(b).toLowerCase() === target);
  if (idx >= 0) return idx;
  return Number.isInteger(options.defaultId) ? options.defaultId : 0;
}

let __origShowMessageBox = null;

async function cmd_stub_message_box(args) {
  const { choice = 'OK', once = true } = args || {};
  const electron = require('electron');
  const dialog = electron.dialog;

  if (!__origShowMessageBox) __origShowMessageBox = dialog.showMessageBox;

  dialog.showMessageBox = async function (browserWindowOrOpts, maybeOpts) {
    const opts = (maybeOpts && maybeOpts.buttons) ? maybeOpts : browserWindowOrOpts;
    const response = pickButtonIndex(choice, opts);
    // 一次性 stub：执行一次后还原
    if (once) {
      dialog.showMessageBox = __origShowMessageBox;
      __origShowMessageBox = null;
    }
    return { response, checkboxChecked: false };
  };

  return { ok: true };
}

// 在分发里加一条
// case 'stub_message_box': return await cmd_stub_message_box(args);


// ---------- JSONL 协议 ----------
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
const send = (obj) => process.stdout.write(JSON.stringify(obj) + '\n');

async function handle(line) {
  if (!line || !line.trim()) return;
  let msg;
  try { msg = JSON.parse(line); } catch {
    return send({ ok: false, error: 'bad json' });
  }
  const { cmd, args = {} } = msg;

  try {
    switch (cmd) {

      // ========== 启动应用 ==========
      case 'launch_app': {
        const name = args.name || 'main';
        const exe  = args.path || args.executablePath;
        const a    = Array.isArray(args.args) ? args.args : [];
        const wait = Number.isFinite(args.waitMs) ? args.waitMs : 12000;
        if (!exe) throw new Error('launch_app requires path/executablePath');

        if (currentApp) { try { await currentApp.close(); } catch {} currentApp = null; currentPage = null; }

        currentApp = await electron.launch({ executablePath: exe, args: a });
        await sleep(wait);
        currentPage = await firstWindow(currentApp);
        return send({ ok: true, result: { name } });
      }

      // ========== 等待 / 点击 / 文本 / 按键（页面内）==========
      case 'wait_for': {
        if (!currentPage) throw new Error('no active page');
        const selector = args.selector;
        const timeout  = Number.isFinite(args.timeout) ? args.timeout : 10000;
        await bringToFrontAndWait(currentPage, selector, timeout);
        return send({ ok: true });
      }

      case 'click': {
        if (!currentPage) throw new Error('no active page');
        const selector = args.selector;
        const timeout  = Number.isFinite(args.timeout) ? args.timeout : 10000;
        await safeClick(currentPage, selector, timeout);
        return send({ ok: true });
      }

      case 'type': {
        if (!currentPage) throw new Error('no active page');
        const selector = args.selector;               // 允许为空（为空则向当前焦点输入）
        const text     = (args.text ?? '').toString();
        const delay    = Number.isFinite(args.delay) ? args.delay : 0;
        const timeout  = Number.isFinite(args.timeout) ? args.timeout : 10000;

        if (selector && selector.trim()) {
          await currentPage.waitForSelector(selector, { state: 'visible', timeout });
          const el = currentPage.locator(selector);
          await el.click({ timeout });
          if (args.replace === true) {
            await el.fill(text, { timeout });
          } else {
            await el.type(text, { delay, timeout });
          }
        } else {
          await currentPage.keyboard.type(text, { delay });
        }
        return send({ ok: true });
      }

      case 'press': {
        if (!currentPage) throw new Error('no active page');
        const selector = args.selector;
        const rawKeys  = (args.keys ?? args.key ?? '').toString();
        const times    = Number.isFinite(args.times) ? args.times : 1;
        const timeout  = Number.isFinite(args.timeout) ? args.timeout : 10000;

        if (!rawKeys) throw new Error('press requires "keys" (e.g. "Enter" / "Control+S")');

        const keys = normalizeKeyCombo(rawKeys);  // ★ 关键：标准化键名

        if (selector && selector.trim()) {
          await currentPage.waitForSelector(selector, { state: 'visible', timeout });
          await currentPage.locator(selector).click({ timeout }); // 让目标获得焦点
        } else {
          try { await currentPage.bringToFront(); } catch {}
        }

        for (let i = 0; i < times; i++) {
          await currentPage.keyboard.press(keys);
        }
        return send({ ok: true });
      }

      case 'stub_message_box':
        return await cmd_stub_message_box(args);

      case 'sleep': {
        const ms = Number.isFinite(args.ms) ? args.ms : 500;
        await sleep(ms);
        return send({ ok: true, result: `slept ${ms}ms` });
      }

      // ========== 受控重启 Studio ==========
      case 'chain_relaunch': {
        const exePath = args.exePath;
        if (!exePath) throw new Error('chain_relaunch requires args.exePath');

        const procName = args.processName || 'MaxOpticsStudio.exe';
        const waitMs   = Number.isFinite(args.waitMs) ? args.waitMs : 8000;

        // 捕获由 launcher 拉起的 Studio 进程
        let info = null, deadline = Date.now() + 15000;
        while (Date.now() < deadline) {
          info = findSpawnedStudio();
          if (info) break;
          await sleep(500);
        }
        if (!info) throw new Error(`no ${procName} process found (spawned by launcher)`);

        const argv = splitCmdLine(info.cmdline);
        const argsFromLauncher = stripDevtoolsFlags(argv.slice(1));
        await sleep(1000);

        // 结束“不可控”的 Studio，改为受控启动
        try { cp.execSync(`taskkill /IM ${procName} /F`); } catch {}

        if (currentApp) { try { await currentApp.close(); } catch {} currentApp = null; currentPage = null; }
        const finalArgs = stripDevtoolsFlags(argsFromLauncher);
        currentApp = await electron.launch({ executablePath: exePath, args: finalArgs });
        await sleep(waitMs);
        currentPage = await firstWindow(currentApp);

        return send({ ok: true, result: { relaunched: true, args: finalArgs } });
      }

      // ========== 新增：系统弹窗按键输入（Windows）==========
      case 'send_keys': {
        const text = args.text;
        if (text === undefined || text === null) throw new Error('send_keys: "text" is required');
        await sendKeysWindows(String(text));
        return send({ ok: true });
      }

      // ========== 新增：主进程拦截保存对话框 ==========
      case 'stub_save_dialog': {
        if (!currentApp) throw new Error('no active app');
        const filePath = (args.filePath || args.path || '').toString().trim();
        if (!filePath) throw new Error('stub_save_dialog requires "filePath"');

        await currentApp.evaluate(({ dialog }, fp) => {
          const g = global;
          if (!g.__origShowSaveDialog)     g.__origShowSaveDialog     = dialog.showSaveDialog;
          if (!g.__origShowSaveDialogSync) g.__origShowSaveDialogSync = dialog.showSaveDialogSync;

          dialog.showSaveDialog = async () => ({ canceled: false, filePath: fp });
          dialog.showSaveDialogSync = () => fp;
          return true;
        }, filePath);

        return send({ ok: true, result: { stubbed: true, filePath } });
      }

      case 'restore_dialog': {
        if (!currentApp) throw new Error('no active app');

        await currentApp.evaluate(({ dialog }) => {
          const g = global;
          if (g.__origShowSaveDialog)     dialog.showSaveDialog     = g.__origShowSaveDialog;
          if (g.__origShowSaveDialogSync) dialog.showSaveDialogSync = g.__origShowSaveDialogSync;
          return true;
        });

        return send({ ok: true, result: { restored: true } });
      }

      // ========== 退出 ==========
      case 'exit': {
        try { if (currentApp) await currentApp.close(); } catch {}
        currentApp = null; currentPage = null;
        return send({ ok: true });
      }


      case 'kill_process': {
        const name = (args && args.name || '').trim();
        if (!name) throw new Error('kill_process: args.name is required');

        const isWin = process.platform === 'win32';
        try {
          if (isWin) {
            // /F 强制，/T 终止子进程
            cp.execSync(`taskkill /IM "${name}" /F /T`, { stdio: 'ignore' });
          } else {
            // Linux/macOS：-9 强制，-f 按命令行匹配更鲁棒
            try { cp.execSync(`pkill -9 -f "${name}"`, { stdio: 'ignore' }); } catch {}
            try { cp.execSync(`killall -9 "${name}"`,    { stdio: 'ignore' }); } catch {}
          }
        } catch (e) {
          // 进程不存在也视为成功（幂等）
        }
        await sleep(800); // 给 OS 一点点时间回收
        return send({ ok: true });
      }



      default:
        return send({ ok: false, error: `Unknown cmd: ${cmd}` });
    }
  } catch (e) {
    return send({ ok: false, error: e.message || String(e), stack: e.stack || '' });
  }
}

rl.on('line', handle);
